using System.Threading;

namespace System
{
    /// <summary>
    /// <see cref="IAsyncResult">IAsyncResult</see> generic implementation.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class AsyncResult<T> : IAsyncResult
    {
        private T _result;

        private static class States
        {
            public const int Pending = 0;
            public const int CompletedSynchronously = 1;
            public const int CompletedAsynchronously = 2;
        }

        private readonly object _syncRoot = new object();
        private readonly AsyncCallback _asyncCallback;
        private readonly object _asyncState;
        private int _completedState = States.Pending;
        private ManualResetEvent _asyncWaitHandle;
        private Exception _exception;

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="asyncCallback"></param>
        /// <param name="state"></param>
        public AsyncResult(AsyncCallback asyncCallback, object state)
        {
            _asyncCallback = asyncCallback;
            _asyncState = state;
        }

        /// <summary>
        /// Sets completed state.
        /// </summary>
        /// <param name="exception"><see cref="Exception"/> to be provided.</param>
        /// <param name="completedSynchronously">Flag.</param>
        public void SetAsCompleted(Exception exception, bool completedSynchronously)
        {
            _exception = exception;

            var prevState = Interlocked.Exchange(ref _completedState,
                                                 completedSynchronously
                                                     ? States.CompletedSynchronously
                                                     : States.CompletedAsynchronously);
            
            if (prevState != States.Pending)
            {
                throw new InvalidOperationException();
            }

            if (_asyncWaitHandle != null)
            {
                _asyncWaitHandle.Set();
            }

            if (_asyncCallback != null)
            {
                _asyncCallback(this);
            }
        }

        /// <summary>
        /// Sets completed state.
        /// </summary>
        /// <param name="result">Result to be provided</param>
        /// <param name="completedSynchronously">Flag.</param>
        public void SetAsCompleted(T result, bool completedSynchronously)
        {
            _result = result;

            SetAsCompleted(null, completedSynchronously);
        }

        /// <summary>
        /// Completes async operation.
        /// </summary>
        /// <returns></returns>
        public T Complete()
        {
            if (!IsCompleted)
            {
                AsyncWaitHandle.WaitOne();
                AsyncWaitHandle.Close();
                _asyncWaitHandle = null;
            }

            if (_exception != null)
            {
                throw _exception;
            }

            return _result;
        }

        #region Implementation of IAsyncResult

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation has completed.
        /// </summary>
        /// <returns>
        /// true if the operation is complete; otherwise, false.
        /// </returns>
        public bool IsCompleted
        {
            get
            {
                bool _;

                lock (_syncRoot)
                {
                    _ = _completedState != States.Pending;
                }

                return _;
            }
        }

        /// <summary>
        /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
        /// </summary>
        /// <returns>
        /// A wait handle that is used to wait for an asynchronous operation to complete.
        /// </returns>
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (_asyncWaitHandle == null)
                {
                    var done = IsCompleted;

                    using (var mre = new ManualResetEvent(done))
                    {
                        if (Interlocked.CompareExchange(
                            ref _asyncWaitHandle, mre, null) == null)
                        {
                            if (!done && IsCompleted)
                            {
                                _asyncWaitHandle.Set();
                            }
                        }
                    }
                }

                return _asyncWaitHandle;
            }
        }

        /// <summary>
        /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
        /// </summary>
        /// <returns>
        /// A user-defined object that qualifies or contains information about an asynchronous operation.
        /// </returns>
        public object AsyncState
        {
            get { return _asyncState; }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation completed synchronously.
        /// </summary>
        /// <returns>
        /// true if the asynchronous operation completed synchronously; otherwise, false.
        /// </returns>
        public bool CompletedSynchronously
        {
            get
            {
                bool _;

                lock (_syncRoot)
                {
                    _ = _completedState == States.CompletedSynchronously;
                }

                return _;
            }
        }

        #endregion
    }
}